/***
 * * PROJECT
 * *    Logical Neighborhood Routing
 * * VERSION
 * *    $Revision: 1.17 $
 * * DATE
 * *    $Date: 2006/12/14 22:26:36 $
 * * LOG
 * *    $Log: LogicalNeighborhood.java,v $
 * *    Revision 1.17  2006/12/14 22:26:36  lmottola
 * *    LNScope becomes an interface that includes matches().
 * *
 * *    Revision 1.16  2006/12/14 18:59:28  lmottola
 * *    Added comments.
 * *
 * *    Revision 1.15  2006/12/14 05:15:15  lmottola
 * *    Moved Estimator in atag.compiler and various cleanup.
 * *
 * *    Revision 1.14  2006/11/05 21:13:15  lmottola
 * *    Bug fixes.
 * *
 * *    Revision 1.13  2006/11/04 02:39:59  lmottola
 * *    Implemented topology creation for SWANS.
 * *
 * *    Revision 1.12  2006/11/03 23:08:12  lmottola
 * *    Traffic application beta running.
 * *
 * *    Revision 1.11  2006/10/27 01:28:27  lmottola
 * *    Debugging support added.
 * *
 * *    Revision 1.10  2006/10/26 23:58:30  lmottola
 * *    Bug fixes: Hello World application running in SWANS.
 * *
 * *    Revision 1.9  2006/10/26 01:27:45  lmottola
 * *    Introduced random scattering of node startup.
 * *
 * *    Revision 1.8  2006/10/26 01:06:45  lmottola
 * *    Added random timeouts on retransmission.
 * *
 * *    Revision 1.7  2006/10/25 22:50:26  lmottola
 * *    SSD routing debugged,
 * *
 * *    Revision 1.6  2006/10/24 19:20:21  lmottola
 * *    Added dedicated scope definition (LNScope) instead of ScopedChannel.
 * *
 * *    Revision 1.5  2006/10/24 16:46:01  lmottola
 * *    Chenged putData in sendData.
 * *
 * *    Revision 1.4  2006/10/24 01:00:18  lmottola
 * *    SSD routing in place.
 * *
 * *    Revision 1.3  2006/10/20 23:53:40  lmottola
 * *    Initial LN code in.
 * *
 * *    Revision 1.2  2006/10/19 21:21:52  lmottola
 * *    Added CVS preamble.
 * *
 * *
 * *   Logical Neighborhoods Routing - Java Version
 * *                                
 * *
 * *   This program is free software; you can redistribute it and/or
 * *   modify it under the terms of the GNU General Public License
 * *   as published by the Free Software Foundation; either version 2
 * *   of the License, or (at your option) any later version.
 * *
 * *   This program is distributed in the hope that it will be useful,
 * *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 * *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * *   GNU General Public License for more details.
 * *
 * *   You should have received a copy of the GNU General Public License
 * *   along with this program; if not, you may find a copy at the FSF web
 * *   site at 'www.gnu.org' or 'www.fsf.org', or you may write to the
 * *   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * *   Boston, MA  02111-1307, USA
 ***/

package atag.runtime.ln;

import java.util.Random;
import java.util.Vector;

import atag.runtime.DataItem;
import atag.runtime.DataPool;
import atag.runtime.NodeInfo;
import atag.runtime.ln.messages.AppMsg;
import atag.runtime.ln.messages.NeighborhoodMessage;
import atag.runtime.ln.messages.ProfAdv;
import atag.runtime.ln.predicates.IntegerSimplePredicate;
import atag.runtime.ln.predicates.LNPredicate;
import atag.runtime.ln.swans.SimulationReferences;

/**
 * @author Luca Mottola <a
 *         href="mailto:mottola@elet.polimi.it">mottola@elet.polimi.it</a>
 * 
 */
public class LogicalNeighborhood implements ObjectIOListener {

	private NeighborSet neighborSet;

	private SSD ssd;

	private LNRouting appRouting;

	private DatagramObjectIO objectIO;

	private static final int PROFADV_DISSEMINATION = 3000;

	protected static final int EXPIRATION_TICKS = 3;

	public static final int PORT = 5555;

	public static final int BUFFER_SIZE = 512;

	public static final int RECEIVE_TIMEOUT = 500;

	private static final byte DISSEMINATION_PARAM = 3;

	private static final int MAX_DELAY = 1000;

	private Random rnd;

	private DataPool dataPool;

	private NodeInfo nodeInfo;

	private Vector messageQueue;

	private long ticks;

	public LogicalNeighborhood(NodeInfo info) {

		this.ticks = 0;
		this.neighborSet = new NeighborSet(EXPIRATION_TICKS);
		this.nodeInfo = info;
		this.ssd = new SSD(info, neighborSet, this);
		this.appRouting = new LNRouting(info, neighborSet, DISSEMINATION_PARAM,
				this);
		this.objectIO = new DatagramObjectIO(PORT, BUFFER_SIZE, RECEIVE_TIMEOUT);
		objectIO.addListener(this);
		this.rnd = new Random(info.getMyId());
		this.messageQueue = new Vector();
	}

	public LogicalNeighborhood(NodeInfo info, DatagramObjectIO objectIO) {

		this.ticks = 0;
		this.neighborSet = new NeighborSet(EXPIRATION_TICKS);
		this.objectIO = objectIO;
		this.nodeInfo = info;
		this.ssd = new SSD(info, neighborSet, this);
		this.appRouting = new LNRouting(info, neighborSet, DISSEMINATION_PARAM,
				this);
		objectIO.addListener(this);
		this.rnd = new Random(info.getMyId());
		this.messageQueue = new Vector();
	}

	// For SWANS simulation
	public static void main(String[] args) {
		LogicalNeighborhood ln;

		if (args[0].equals("sendTypeA")) {
			ln = SimulationReferences.getLN(Integer.parseInt(args[1]));
			LNPredicate ofTypeA = new IntegerSimplePredicate("type",
					IntegerSimplePredicate.EQUAL, 0);
			LNScope typeA = new ConjunctiveScope(new LNPredicate[] { ofTypeA });
			ln.sendData(new DataItem(0, 0, null), new LNScope[] { typeA });
			ln.nodeInfo.debugPrint("Sending message to nodes of type A");
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			ln = SimulationReferences.getLN(Integer.parseInt(args[1]));
			ln.sendData(new DataItem(0, 0, null), new LNScope[] { typeA });
			ln.nodeInfo.debugPrint("Sending message to nodes of type A");
		} else if (args[0].equals("sendTypeB")) {
			ln = SimulationReferences.getLN(Integer.parseInt(args[1]));
			LNPredicate ofTypeB = new IntegerSimplePredicate("type",
					IntegerSimplePredicate.EQUAL, 1);
			LNScope typeB = new ConjunctiveScope(new LNPredicate[] { ofTypeB });
			ln.sendData(new DataItem(0, 0, null), new LNScope[] { typeB });
			ln.nodeInfo.debugPrint("Sending message to nodes of type B");
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			ln = SimulationReferences.getLN(Integer.parseInt(args[1]));
			ln.sendData(new DataItem(0, 0, null), new LNScope[] { typeB });
			ln.nodeInfo.debugPrint("Sending message to nodes of type B");
		} else if (args[0].equals("sendTypeC")) {
			ln = SimulationReferences.getLN(Integer.parseInt(args[1]));
			LNPredicate ofTypeC = new IntegerSimplePredicate("type",
					IntegerSimplePredicate.EQUAL, 2);
			LNScope typeC = new ConjunctiveScope(new LNPredicate[] { ofTypeC });
			ln.sendData(new DataItem(0, 0, null), new LNScope[] { typeC });
			ln.nodeInfo.debugPrint("Sending message to nodes of type C");

			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			ln = SimulationReferences.getLN(Integer.parseInt(args[1]));
			ln.sendData(new DataItem(0, 0, null), new LNScope[] { typeC });
			ln.nodeInfo.debugPrint("Sending message to nodes of type C");

		} else if (args[0].equals("SSDTest")) {

			ln = SimulationReferences.getLN(Integer.parseInt(args[1]));
			try {
				Thread.sleep(ln.rnd.nextInt(PROFADV_DISSEMINATION));
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			ln.ssd.disseminateProfAdv(1);
		} else if (args[0].equals("tickStart")) {
			ln = SimulationReferences.getLN(Integer.parseInt(args[1]));
			ln.nodeInfo.debugPrint("Starting LN Tick thread");
			ln.tick();
		} else if (args[0].equals("queueStart")) {
			ln = SimulationReferences.getLN(Integer.parseInt(args[1]));
			ln.nodeInfo.debugPrint("Starting LN Queue thread");
			ln.queueSimSend();
		}
	}

	public void sendData(DataItem data, LNScope[] scopes) {
		appRouting.generateAppMsg(data, scopes);
	}

	public void objectReceived(Object o) {

		if (!(o instanceof NeighborhoodMessage)) {
			nodeInfo.debugPrint("Unknown message received.");
		} else {
			NeighborhoodMessage msg = (NeighborhoodMessage) o;
			if (msg.getOriginatorId() != nodeInfo.getMyId()
					&& msg.getSenderId() != nodeInfo.getMyId()) {
				neighborSet.update(msg.getSenderId(), ticks);
				if (msg instanceof ProfAdv) {
					nodeInfo.debugPrint("Got ProfAdv message from "
							+ msg.getSenderId() + " generated from "
							+ msg.getOriginatorId());
					ssd.parseProfAdv((ProfAdv) msg, ticks);
				} else if (msg instanceof AppMsg) {
					nodeInfo.debugPrint("Got AppMsg message from "
							+ msg.getSenderId() + " generated from "
							+ msg.getOriginatorId());
					appRouting.parseAppMsg((AppMsg) msg);
				} else {
					System.err.println("Unknown message received.");
				}
			}
		}
	}

	protected void sendMessage(NeighborhoodMessage msg) {
		msg.setSenderId(nodeInfo.getMyId());
		messageQueue.add(messageQueue.size(), msg);
	}

	protected void forwardMessage(NeighborhoodMessage msg) {
		msg.setHops((byte) (msg.getHops() + 1));
		sendMessage(msg);
	}

	private void tick() {
		while (true) {
			ticks++;
			neighborSet.pruneExpiredNeighbors(ticks);
			// ssd.disseminateProfAdv(logicalTime);
			try {
				Thread.sleep(PROFADV_DISSEMINATION);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	private void queueSimSend() {
		while (true) {
			if (!messageQueue.isEmpty()) {
				nodeInfo.debugPrint("Transmiting message");
				objectIO.sendBroadcastObject(messageQueue.firstElement());
				messageQueue.remove(0);
			}
			int delay = rnd.nextInt(MAX_DELAY);
			// nodeInfo.debugPrint("Waiting " + delay + " before transmitting");
			// To avoid collisions at the network level
			try {
				Thread.sleep(delay);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			// nodeInfo.debugPrint("Waited " + delay + ", it was enough");
		}
	}

	protected void deliver(DataItem data) {

		nodeInfo.debugPrint("Delivering data item");
		dataPool.putDataFromNetwork(data);
	}

	public void setDataPool(DataPool dataPool) {
		this.dataPool = dataPool;
	}
}
